/*
 * in_uvc.c
 *
 *  Created on: Apr 4, 2015
 *      Author: odroid
 */
/*
 * mfc codec encoding example application
 * Andrzej Hajda <a.hajda@samsung.com>
 *
 * Sample input device.
 *
 * Copyright 2012 Samsung Electronics Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>

#include "common.h"
#include "in_uvc.h"
#include "func_dev.h"

//#define UVC_MMAP

static unsigned char **userptr_buf;
static unsigned int userptr_size;

static struct io_dev_ops in_uvc_ops = { .req_bufs = uvc_req_bufs,
					   .enq_buf = uvc_enq_buf,
					   .deq_buf = uvc_deq_buf,
					   .destroy = uvc_destroy
				};
int uvc_destroy(struct io_dev *dev)
{
	struct v4l2_buffer buf;
	if(!dev)
		return 0;

	memzero(buf);

	buf.memory = io_type_to_memory(dev->io[DIR_OUT].type);
	free(userptr_buf);


	if (dev->io[DIR_IN].type == IO_MMAP)
		dev_bufs_destroy(dev->io[DIR_IN].bufs);

	if (dev->io[DIR_OUT].type == IO_MMAP)
		dev_bufs_destroy(dev->io[DIR_OUT].bufs);



	if (dev->fd >= 0)
		close(dev->fd);

	free(dev);

	return 0;
}

int uvc_req_bufs(struct io_dev *dev, enum io_dir dir, int nelem)
{
	int ret;
	int i;
	struct v4l2_requestbuffers reqbuf;
	struct v4l2_buffer buf;

	memzero(reqbuf);

	reqbuf.count = nelem;
	if(dev->no_mplane_flag)
		reqbuf.type = io_dir_to_nomplane_type(dir);
	else
		reqbuf.type = io_dir_to_type(dir);

#ifdef UVC_MMAP
	reqbuf.memory = V4L2_MEMORY_MMAP;
#else
	reqbuf.memory = io_type_to_memory(dev->io[dir].type);
#endif

	ret = ioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf);
	if (ret != 0) {
		err("Failed to request %d buffers for device %d:%d)", nelem,
							dev->fd, dir);
		return -1;
	}

	if(reqbuf.memory ==  V4L2_MEMORY_USERPTR) {
		userptr_buf = malloc(sizeof(*userptr_buf) * reqbuf.count);
		if(userptr_buf == NULL){
			err("Failed to request *userptr_buf for device %d:%d)", dev->fd, dir);
			return -1;
		}
		for(i = 0; i < reqbuf.count; i++) {
			userptr_buf[i] = (unsigned char *)malloc(sizeof(unsigned char) * userptr_size);
			if(userptr_buf[i] == NULL) {
				err("Failed to request userptr_buf for device %d:%d)", dev->fd, dir);
				return -1;
			}
		}
	}
	else if(reqbuf.memory ==  V4L2_MEMORY_MMAP){
		userptr_buf = malloc(sizeof(*userptr_buf) * reqbuf.count);
		if(userptr_buf == NULL) {
			err("Failed to request *userptr_buf for device %d:%d)", dev->fd, dir);
			return -1;
		}
		for(i = 0; i < reqbuf.count; i++) {
			memzero(buf);
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			buf.memory = V4L2_MEMORY_MMAP;
			buf.index = i;
			ret = ioctl(dev->fd, VIDIOC_QUERYBUF, &buf);
			if (ret != 0) {
				err("QUERYBUF failed");
				free(userptr_buf);
				return -1;
			}

			userptr_size = buf.length;
			userptr_buf[i] = mmap(NULL, userptr_size, PROT_READ | PROT_WRITE, MAP_SHARED, dev->fd, buf.m.offset);
			if(userptr_buf[i] == NULL) {
				err("Failed to request userptr_buf for device %d:%d)", dev->fd, dir);
				free(userptr_buf);
				return -1;
			}
		}
	}


	dbg("Succesfully requested %d buffers for device %d:%d", nelem,
								dev->fd, dir);

	return reqbuf.count;
}

int uvc_deq_buf(struct io_dev *dev, enum io_dir dir)
{

	enum v4l2_buf_type type;
	struct v4l2_buffer buf;
	int ret;
	int idx;
	struct dev_buffers *bufs;
	int i;

	bufs = dev->io[dir].bufs;

	memzero(buf);

	buf.type = io_dir_to_nomplane_type(dir);

#ifdef UVC_MMAP
	buf.memory = V4L2_MEMORY_MMAP;
#else
	buf.memory = io_type_to_memory(dev->io[dir].type);
#endif
//	buf.m.planes = planes;
//	buf.length = MFC_MAX_PLANES;

	ret = ioctl(dev->fd, VIDIOC_DQBUF, &buf);
	if (ret != 0) {
		dbg("Dequeue buffer error for %d:%d", dev->fd, dir);
		return -1;
	} else
		dbg("Dequeued %d/%d buffer(flags=%02X,s=%d) from %d:%d",
					buf.index, bufs->count, buf.flags,
					buf.bytesused, dev->fd,
					dir);

	idx = buf.index;

	for (i = 0; i < bufs->nplanes; ++i){
		bufs->bytesused[idx * bufs->nplanes + i] =
						buf.bytesused;
		bufs->lengths[i] = buf.length;
	}

	if(buf.memory != V4L2_MEMORY_DMABUF)
		memcpy(bufs->addr[idx], userptr_buf[idx], userptr_size);

	--dev->io[dir].nbufs;

	++dev->io[dir].counter;

	if (dev->io[dir].limit && dev->io[dir].limit <= dev->io[dir].counter) {
		dev->io[dir].state = FS_END;
		if (dev->io[DIR_OUT - dir].type == IO_NONE ||
					dev->io[DIR_OUT - dir].state == FS_END)
			v4l_stream_set(dev, 0);
		dbg("End on %d:%d", dev->fd, dir);
	} else {
		dev->io[dir].state = FS_BUSY;
	}

	if (dir == DIR_IN && dev->io[DIR_IN].state == FS_END
						&& !dev->io[DIR_OUT].limit)
		dev->io[DIR_OUT].limit = dev->io[DIR_IN].counter;

	return idx;
}

int uvc_enq_buf(struct io_dev *dev, enum io_dir dir, int idx)
{
//	struct v4l2_plane planes[MFC_MAX_PLANES];
	struct v4l2_buffer buf;
	int i;
	int ret;
	struct dev_buffers *bufs;

	bufs = dev->io[dir].bufs;

	memzero(buf);
	if(dev->no_mplane_flag)
		buf.type = io_dir_to_nomplane_type(dir);
	else
		buf.type = io_dir_to_type(dir);

#ifdef UVC_MMAP
	buf.memory = V4L2_MEMORY_MMAP;
#else
	buf.memory = io_type_to_memory(dev->io[dir].type);
#endif

	buf.index = idx;
//	buf.m.planes = planes;
//	buf.length = bufs->lengths[0];
	if(dev->io[dir].type == IO_USERPTR){
		for (i = 0; i < bufs->nplanes; ++i) {
			buf.bytesused = bufs->bytesused[idx * bufs->nplanes + i];
			buf.length = bufs->lengths[i];
			buf.m.userptr = (unsigned int)userptr_buf[idx];//(unsigned long)bufs->addr[idx * bufs->nplanes + i];
		}
	}else if(dev->io[dir].type == IO_DMABUF){//for single plane
	    buf.index = idx;
	    buf.m.fd = bufs->dmafd[idx][0];
	}

	ret = ioctl(dev->fd, VIDIOC_QBUF, &buf);
	if (ret != 0) {
		err("Error %d enq buffer %d/%d to %d:%d", errno, idx,
						bufs->count, dev->fd, dir);
		return -1;
	} else {
		dbg("Enqueued buffer %d/%d to %d:%d", idx, bufs->count,
								dev->fd, dir);
	}

	++dev->io[dir].nbufs;

	if (dev->io[dir].state == FS_OFF)
		if (dir == DIR_IN || dev->io[DIR_IN].type == IO_NONE)
			v4l_stream_set(dev, 1);

	return 0;
}

/* set format with proper alignement */
int uvc_set_fmt(struct io_dev *dev, enum io_dir dir, int width, int height)
{
	struct v4l2_format fmt;
	struct v4l2_buffer buf;
	int ret;

	memzero(buf);
#ifdef UVC_MMAP
	buf.memory = V4L2_MEMORY_MMAP;
#else
	buf.memory = io_type_to_memory(dev->io[dir].type);
#endif

	memzero(fmt);

	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	fmt.fmt.pix.width = width;
	fmt.fmt.pix.height = height;
	fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
	fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

	ret = ioctl(dev->fd, VIDIOC_S_FMT, &fmt);
	if (ret != 0)
		err("Cannot set format on %d:%d, width:%d, height:%d", dev->fd, dir, width, height);

	if(buf.memory ==  V4L2_MEMORY_USERPTR){
		if(fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
			userptr_size = width * height * 2;
	}

	return ret;
}

int uvc_set_rate(struct io_dev *dev, int rate)
{
	struct v4l2_streamparm fps;
	int ret;

	fps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fps.parm.capture.capturemode = 0;
    fps.parm.capture.timeperframe.numerator = 1;
    fps.parm.capture.timeperframe.denominator = rate;
	ret = ioctl(dev->fd, VIDIOC_S_PARM, &fps);
	if (ret != 0)
		err("Cannot set rate on %d", dev->fd);

	return ret;
}

struct io_dev *in_uvc_create(char const *name)
{
	struct io_dev *dev;

	dev = malloc(sizeof(*dev));
	memzero(*dev);

	dev->fd = open(name, O_RDWR, 0);
	if (dev->fd < 0) {
		free(dev);
		err("Cannot open camera(%s) device", name);
		return NULL;
	}

	dev->io[DIR_IN].type = IO_NONE;
	dev->io[DIR_OUT].type = IO_DMABUF;
	dev->no_mplane_flag = 1;

	dev->ops = &in_uvc_ops;

	dbg("UVC device %s opened with fd=%d", name, dev->fd);
	return dev;
}
